package com.haha.lock;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

/**
 * 分布式锁的抽象模板类
 *
 * <p>该类采用<b>模板方法模式 (Template Method Pattern)</b>，定义了分布式锁的核心算法骨架，
 * 将诸如密钥构建、参数校验等通用逻辑封装在此抽象类中，而将具体的加锁/解锁实现延迟到子类。</p>
 *
 * <p><b>核心设计思想：</b></p>
 * <ul>
 *   <li><b>模板方法</b>：{@link #executeWithLock(String, long, long, TimeUnit, Supplier)} 方法
 *       定义了“尝试加锁 → 执行业务 → 释放锁”的固定流程。</li>
 *   <li><b>钩子方法</b>：{@code tryLock}, {@code unlock} 等具体锁操作由子类实现，允许灵活支持不同技术（如 Redis, Zookeeper）。</li>
 *   <li><b>代码复用</b>：公共逻辑（如密钥规范、参数验证）在抽象类中实现，避免子类重复编码。</li>
 * </ul>
 *
 * <p>子类只需继承此类并实现抽象的锁操作方法，即可快速实现基于不同技术的分布式锁，符合“开闭原则”。</p>
 *
 * @author zouwz5
 * @date 2025/10/8 11:17
 * @version 1.0
 * @see DistributedLock
 */
public abstract class AbstractDistributedLock implements DistributedLock {

    /**
     * 构建完整的分布式锁键（Key）
     *
     * <p>为原始的锁标识符添加统一前缀，确保在 Redis 或类似存储中键的命名空间清晰、易于管理，
     * 避免与其他业务键名冲突。例如，将 "order_123" 转换为 "lock:order_123"。</p>
     *
     * @param lockKey 业务层传入的原始锁标识符（例如订单ID、用户ID等）
     * @return 添加了统一命名空间前缀后的完整锁键
     */
    protected String buildFullLockKey(String lockKey) {
        // 这里可以统一添加前缀，规范密钥格式，例如: "distributed_lock:business_name:key"
        return "distributedLock:" + lockKey;
    }

    /**
     * 验证分布式锁的相关参数是否合法
     *
     * <p>在尝试获取锁之前进行前置校验，确保输入参数的合法性，避免因参数错误导致不可预期的行为。
     * 此方法应在具体锁实现尝试获取锁前被调用。</p>
     *
     * @param lockKey   锁的标识符，不能为 null 或空字符串
     * @param waitTime  获取锁的最大等待时间，必须为非负数
     * @param leaseTime 锁的持有时间（租期），必须为正数
     * @param unit      时间单位，不能为 null
     * @throws IllegalArgumentException 当任何参数不符合要求时抛出
     */
    protected void validateLockParameters(String lockKey, long waitTime, long leaseTime, TimeUnit unit) {
        if (lockKey == null || lockKey.trim().isEmpty()) {
            throw new IllegalArgumentException("Lock key cannot be null or empty");
        }
        if (waitTime < 0) {
            throw new IllegalArgumentException("Wait time must be non-negative");
        }
        if (leaseTime <= 0 && leaseTime != -1) {
            throw new IllegalArgumentException("Lease time must be positive or -1");
        }
        if (unit == null) {
            throw new IllegalArgumentException("Time unit cannot be null");
        }
    }

    /**
     * 模板方法：在分布式锁保护下执行包含返回值的业务逻辑
     *
     * <p>此方法定义了使用分布式锁的标准流程模板，确保了锁的获取和释放操作的正确性和安全性：</p>
     * <ol>
     *   <li>构建完整的锁键。</li>
     *   <li>尝试获取分布式锁。</li>
     *   <li>若获取成功，执行传入的业务逻辑 ({@link Supplier#get()})。</li>
     *   <li>无论业务逻辑执行成功或异常，最终在 {@code finally} 块中检查并释放锁。</li>
     * </ol>
     *
     * <p><b>注意：</b>此方法通过 {@code finally} 块保证锁一定被释放，防止死锁。释放前会校验当前线程是否仍持有锁，避免误释放。</p>
     *
     * @param <T>        业务逻辑执行结果的类型
     * @param lockKey    锁的业务标识符
     * @param waitTime   获取锁的等待时间
     * @param leaseTime  锁的自动释放时间
     * @param unit       时间单位
     * @param supplier   需要被保护执行的有返回值的业务逻辑
     * @return 业务逻辑的执行结果
     * @throws DistributedLockException 当获取锁失败时抛出
     * @see #tryLock(String, long, long, TimeUnit)
     * @see #isHeldByCurrentThread(String)
     * @see #unlock(String)
     */
    @Override
    public <T> T executeWithLock(String lockKey, long waitTime, long leaseTime, TimeUnit unit, Supplier<T> supplier) {
        String fullLockKey = buildFullLockKey(lockKey);
        boolean locked = false;

        try {
            // 尝试获取锁
            locked = tryLock(fullLockKey, waitTime, leaseTime, unit);
            if (!locked) {
                throw new DistributedLockException("Failed to acquire lock for key: " + lockKey);
            }
            // 执行受保护的业务逻辑
            return supplier.get();
        } finally {
            // 确保释放锁
            if (locked && isHeldByCurrentThread(fullLockKey)) {
                unlock(fullLockKey);
            }
        }
    }

    /**
     * 模板方法：在分布式锁保护下执行不包含返回值的业务逻辑
     *
     * <p>此方法是 {@link #executeWithLock(String, long, long, TimeUnit, Supplier)} 的便捷重载，
     * 通过将 {@link Runnable} 适配为 {@link Supplier}，复用相同的锁管理流程，适用于无需返回值的场景。</p>
     *
     * @param lockKey   锁的业务标识符
     * @param waitTime  获取锁的等待时间
     * @param leaseTime 锁的自动释放时间
     * @param unit      时间单位
     * @param runnable  需要被保护执行的业务逻辑
     */
    @Override
    public void executeWithLock(String lockKey, long waitTime, long leaseTime, TimeUnit unit, Runnable runnable) {
        // 将Runnable转换为Supplier，复用上面的模板方法
        executeWithLock(lockKey, waitTime, leaseTime, unit, () -> {
            runnable.run();
            return null;
        });
    }
}